#ifndef LONGKEY_BASE_REG_KEY_H_
#define LONGKEY_BASE_REG_KEY_H_

#include <Windows.h>
#include <vector>
#include "debug.h"
#include "logging.h"
#include "scoped_ptr.h"
#include "scoped_any.h"
#include "store_watcher.h"

// maximum sizes registry key and value names
#define kMaxKeyNameChars   (255 + 1)
#define kMaxValueNameChars (16383 + 1)

class RegKey {
public:
	RegKey();
	virtual ~RegKey();

	// create a reg key
	HRESULT Create(HKEY hKeyParent, const TCHAR * key_name,
		TCHAR * reg_class = REG_NONE, DWORD options = REG_OPTION_NON_VOLATILE,
		REGSAM sam_desired = KEY_ALL_ACCESS,
		LPSECURITY_ATTRIBUTES lp_sec_attr = NULL,
		LPDWORD lp_disposition = NULL);

	// create a reg key, given the full key name, including the HKEY root
	// (say for example, "HKLM\\Software")
	HRESULT Create(const TCHAR * full_key_name,
		TCHAR * reg_class = REG_NONE, DWORD options = REG_OPTION_NON_VOLATILE,
		REGSAM sam_desired = KEY_ALL_ACCESS,
		LPSECURITY_ATTRIBUTES lp_sec_attr = NULL,
		LPDWORD lp_disposition = NULL);

	// static helper function that create a set of reg keys,
	// given an array of full key names including the HKEY root
	// (say for example, "HKLM\\Software")
	static HRESULT CreateKeys(const TCHAR* keys_to_create[],
		DWORD number_of_keys,
		TCHAR* reg_class = REG_NONE,
		DWORD options = REG_OPTION_NON_VOLATILE,
		LPSECURITY_ATTRIBUTES lp_sec_attr = NULL);

	// Static method to create a single key.
	static HRESULT CreateKey(const TCHAR * full_key_name,
		TCHAR * reg_class = REG_NONE,
		DWORD options = REG_OPTION_NON_VOLATILE,
		LPSECURITY_ATTRIBUTES lp_sec_attr = NULL);

	// open an existing reg key
	HRESULT Open(HKEY hKeyParent,
		const TCHAR * key_name,
		REGSAM sam_desired = KEY_ALL_ACCESS);

	// open an existing reg key, given the full key name, including the HKEY root
	// (say for example, "HKLM\\Software")
	HRESULT Open(const TCHAR * full_key_name,
		REGSAM sam_desired = KEY_ALL_ACCESS);

	// close this reg key
	virtual HRESULT Close();

	// check if the key has a specified value
	bool HasValue(const TCHAR * value_name);

	// get the number of values for this key
	uint32 GetValueCount();

	// Called to get the value name for the given value name index
	// Use GetValueCount() to get the total value_name count for this key
	// Returns failure if no key at the specified index
	// If you modify the key while enumerating, the indexes will be out of order.
	// Since the index order is not guaranteed, you need to reset your counting
	// loop.
	// type refers to REG_DWORD, REG_QWORD, etc..
	// 'type' can be NULL if not interested in the value type
	HRESULT GetValueNameAt(int index, CString * value_name, DWORD *type);

	// check if the current key has the specified subkey
	bool HasSubkey(const TCHAR * key_name) const;

	// get the number of subkeys for this key
	uint32 GetSubkeyCount();

	// Called to get the key name for the given key index
	// Use GetSubkeyCount() to get the total count for this key
	// Returns failure if no key at the specified index
	// If you modify the key while enumerating, the indexes will be out of order.
	// Since the index order is not guaranteed, you need to reset your counting
	// loop.
	HRESULT GetSubkeyNameAt(int index, CString * key_name);

	// SETTERS

	// set an int32 value - use when reading multiple values from a key
	HRESULT SetValue(const TCHAR * value_name, DWORD value) const;

	// set an int64 value
	HRESULT SetValue(const TCHAR * value_name, DWORD64 value) const;

	// set a string value
	HRESULT SetValue(const TCHAR * value_name, const TCHAR * value) const;

	// set binary data
	HRESULT SetValue(const TCHAR * value_name,
		const byte * value,
		DWORD byte_count) const;

	// set raw data, including type
	HRESULT SetValue(const TCHAR * value_name,
		const byte * value,
		DWORD byte_count,
		DWORD type) const;

	// GETTERS

	// get type of a registry value
	HRESULT GetValueType(const TCHAR* value_name, DWORD* value_type) const;

	// get an int32 value
	HRESULT GetValue(const TCHAR * value_name, DWORD * value) const;

	// get an int64 value
	//
	// Note: if you are using time64 you should
	// likely use GetLimitedTimeValue (util.h) instead of this method.
	HRESULT GetValue(const TCHAR * value_name, DWORD64 * value) const;

	// get a string value - the caller must free the return buffer
	HRESULT GetValue(const TCHAR * value_name, TCHAR * * value) const;

	// get a CString value
	HRESULT GetValue(const TCHAR* value_name, OUT CString* value) const;

	// get a vector<CString> value from REG_MULTI_SZ type
	HRESULT GetValue(const TCHAR * value_name,
		std::vector<CString> * value) const;

	// get binary data - the caller must free the return buffer
	HRESULT GetValue(const TCHAR * value_name,
		byte * * value,
		DWORD * byte_count) const;

	// get raw data, including type - the caller must free the return buffer
	HRESULT GetValue(const TCHAR * value_name,
		byte * * value,
		DWORD * byte_count,
		DWORD *type) const;

	// RENAMERS

	// Rename a named value.
	HRESULT RenameValue(const TCHAR * old_value_name,
		const TCHAR * new_value_name) const;

	// STATIC VERSIONS

	// flush
	static HRESULT FlushKey(const TCHAR * full_key_name);

	// Check if a key exists.
	static bool HasKey(const TCHAR * full_key_name);

	// Check if a key exists in the native (i.e. non-redirected) registry.
	static bool HasNativeKey(const TCHAR * full_key_name);

	// check if the key has a specified value
	static bool HasValue(const TCHAR * full_key_name, const TCHAR * value_name);

	// SETTERS

	// STATIC int32 set
	static HRESULT SetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		DWORD value);

	// STATIC int64 set
	static HRESULT SetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		DWORD64 value);

	// STATIC float set
	static HRESULT SetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		float value);

	// STATIC double set
	static HRESULT SetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		double value);

	// STATIC string set
	static HRESULT SetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		const TCHAR * value);

	// STATIC binary data set
	static HRESULT SetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		const byte * value,
		DWORD byte_count);

	// STATIC array of strings set
	static HRESULT SetValueMultiSZ(const TCHAR * full_key_name,
		const TCHAR * value_name,
		const byte * value,
		DWORD byte_count);

	// STATIC expandable string set
	static HRESULT SetValueExpandSZ(const TCHAR * full_key_name,
		const TCHAR * value_name,
		const TCHAR * value);

	// GETTERS

	// STATIC int32 get
	static HRESULT GetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		DWORD * value);

	// STATIC int64 get
	//
	// Note: if you are using time64 you should
	// likely use GetLimitedTimeValue (util.h) instead of this method.
	static HRESULT GetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		DWORD64 * value);

	// STATIC float get
	static HRESULT GetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		float * value);

	// STATIC double get
	static HRESULT GetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		double * value);

	// STATIC string get (STR and CString versions) - the caller must free
	// the return buffer
	static HRESULT GetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		TCHAR * * value);

	static HRESULT GetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		CString * value);

	// STATIC REG_MULTI_SZ get
	static HRESULT GetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		std::vector<CString> * value);

	// STATIC get binary data - the caller must free the return buffer
	static HRESULT GetValue(const TCHAR * full_key_name,
		const TCHAR * value_name,
		byte * * value,
		DWORD * byte_count);

	// Try reg keys successively if there is a failure in getting a value.
	//
	// Typically used when there is a user value and a default value if the
	// user has none.
	template<typename T>
	static HRESULT GetValue(const TCHAR * full_key_names[],
		int key_names_length,
		const TCHAR * value_name,
		T* value);

	// RENAMERS

	// Rename a named value.
	static HRESULT RenameValue(const TCHAR * full_key_name,
		const TCHAR * old_value_name,
		const TCHAR * new_value_name);

	// COPIERS

	// The full_to_key must exist for CopyValue to succeed.
	static HRESULT CopyValue(const TCHAR * full_from_key_name,
		const TCHAR * from_value_name,
		const TCHAR * full_to_key_name,
		const TCHAR * to_value_name);

	static HRESULT CopyValue(const TCHAR * full_from_key_name,
		const TCHAR * full_to_key_name,
		const TCHAR * value_name);

	// Get type of a registry value
	static HRESULT GetValueType(const TCHAR* full_key_name,
		const TCHAR* value_name,
		DWORD* value_type);

	// delete a subkey of the current key (with no subkeys)
	HRESULT DeleteSubKey(const TCHAR * key_name);

	// recursively delete a sub key of the current key (and all its subkeys)
	HRESULT RecurseDeleteSubKey(const TCHAR * key_name);

	// STATIC version of delete key - handles nested keys also
	// delete a key and all its sub-keys recursively
	// Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
	// and failure otherwise.
	static HRESULT DeleteKey(const TCHAR* full_key_name);

	// STATIC version of delete key
	// delete a key recursively or non-recursively
	// Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
	// and failure otherwise.
	static HRESULT DeleteKey(const TCHAR* full_key_name, bool recursive);

	// delete the specified value
	HRESULT DeleteValue(const TCHAR * value_name) const;

	// STATIC version of delete value
	// Returns S_FALSE if key didn't exist, S_OK if deletion was successful,
	// and failure otherwise.
	static HRESULT DeleteValue(const TCHAR * full_key_name,
		const TCHAR * value_name);

	// Peek inside (use a RegKey as a smart wrapper around a registry handle)
	HKEY Key() { return h_key_; }

	// Used to help test the private functionality
	friend class RegKeyTestClass;

	// helper function to get the HKEY and the root key from a string
	// representation modifies the argument in place and returns the key name
	// e.g. HKLM\\Software\\Google\... returns HKLM, "Software\\Google\..."
	// Necessary for the static versions that use the full name of the reg key
	static HKEY GetRootKeyInfo(CString * full_key_name);

	// Returns true if this key name is 'safe' for deletion (doesn't specify
	// a key root)
	static bool SafeKeyNameForDeletion(const wchar_t *key_name);

	// save the key and all of its subkeys and values to a file
	static HRESULT Save(const TCHAR* full_key_name, const TCHAR* file_name);

	// restore the key and all of its subkeys and values which are saved into
	// a file
	static HRESULT Restore(const TCHAR* full_key_name, const TCHAR* file_name);

	// Is the key empty: having no sub-keys and values
	static bool IsKeyEmpty(const TCHAR* full_key_name);

private:

	// Helper function to check if a key exists, using the sam flags specified.
	// Note: KEY_READ must be included in sam_flags.
	static bool HasKeyHelper(const TCHAR * full_key_name, DWORD sam_flags);

	// helper function to get the parent key name and the subkey from a string
	// representation modifies the argument in place and returns the key name
	// e.g. Software\\Google\\Foo_Bar returns "Software\\Google", "Foo_Bar"
	// Necessary for the static versions that use the full name of the reg key
	static CString GetParentKeyInfo(CString * key_name);

	// helper function to get any value from the registry
	// used when the size of the data is unknown
	HRESULT GetValueHelper(const TCHAR * value_name,
		DWORD * type,
		byte * * value,
		DWORD * byte_count) const;

	// common SET Helper for the static case
	static HRESULT SetValueStaticHelper(const TCHAR * full_key_name,
		const TCHAR * value_name,
		DWORD type,
		LPVOID value,
		DWORD byte_count = 0);

	// common GET Helper for the static case
	static HRESULT GetValueStaticHelper(const TCHAR * full_key_name,
		const TCHAR * value_name,
		DWORD type,
		LPVOID value,
		DWORD * byte_count = NULL);

	// convert REG_MULTI_SZ bytes to string array
	static HRESULT MultiSZBytesToStringArray(const byte * buffer,
		DWORD byte_count,
		std::vector<CString> * value);

	// set a string or expandable string value
	HRESULT SetStringValue(const TCHAR * value_name,
		const TCHAR * value,
		DWORD type) const;

	// the HKEY for the current key
	HKEY h_key_;

	DISALLOW_EVIL_CONSTRUCTORS(RegKey);
};

// Provides all the functionality of RegKey plus
// an event to watch for changes to the registry key.
class RegKeyWithChangeEvent : public RegKey {
public:
	RegKeyWithChangeEvent() {}
	// close this reg key and the event
	virtual HRESULT Close();

	// Called to create/reset the event that gets signaled
	// any time the registry key changes.  Access the created
	// event using change_event().
	//
	// See the documentation for RegNotifyChangeKeyValue
	// for values for notify_filter.
	HRESULT SetupEvent(bool watch_subtree, DWORD notify_filter);

	// Indicates if any changes (that are being monitored) have occured
	bool HasChangeOccurred() const;

	// Get the event that is signaled on registry changes.
	// Note:
	//   * This event will remain constant until Close() is called.
	//   * One should call SetupEvent to set-up the event.
	//   * The event is only signaled on the next change and remains signaled.
	//     Do not call ::ResetEvent().  Call SetupEvent() to reset
	//     the event and wait for more changes.
	HANDLE change_event() const {
		return get(change_event_);
	}

private:
	scoped_handle change_event_;
	DISALLOW_EVIL_CONSTRUCTORS(RegKeyWithChangeEvent);
};

// Does the common things necessary for watching
// registry key changes.  If there are file change or other watchers,
// there could be a common interface for the three methods to decouple
// the code that is doing the watching from the code that owns the store.
class RegKeyWatcher : public StoreWatcher {
public:
	// reg_key: the full string for the reg key
	// watch_subtree: watch all subkey changes  or
	//                only immediate child values
	// notify_filter: See the documentation for RegNotifyChangeKeyValue
	// allow_creation: Should the key be created if it doesn't exist?
	RegKeyWatcher(const TCHAR* reg_key, bool watch_subtree,
		DWORD notify_filter, bool allow_creation);
	virtual ~RegKeyWatcher() {}

	// Called to create/reset the event that gets signaled
	// any time the registry key changes.  Access the created
	// event using change_event().
	virtual HRESULT EnsureEventSetup();

	// Get the event that is signaled on registry changes.
	virtual HANDLE change_event() const;

private:
	// Used to do the SetupEvent method
	scoped_ptr<RegKeyWithChangeEvent> reg_key_with_change_event_;

	CString reg_key_string_;
	bool watch_subtree_;
	bool allow_creation_;
	DWORD notify_filter_;
	DISALLOW_EVIL_CONSTRUCTORS(RegKeyWatcher);
};


inline RegKey::RegKey() { h_key_ = NULL; }

inline RegKey::~RegKey() { Close(); }

inline bool RegKey::HasValue(const TCHAR* value_name)
{
	return (ERROR_SUCCESS == ::RegQueryValueEx(h_key_,
		value_name,
		NULL, NULL,
		NULL,
		NULL));
}

// SETTERS static versions
inline HRESULT RegKey::SetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								DWORD value)
{
									ASSERT1(full_key_name);

									return SetValueStaticHelper(full_key_name, value_name, REG_DWORD, &value);
}

inline HRESULT RegKey::SetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								DWORD64 value) {
									ASSERT1(full_key_name);

									return SetValueStaticHelper(full_key_name, value_name, REG_QWORD, &value);
}

inline HRESULT RegKey::SetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								float value) {
									ASSERT1(full_key_name);

									return SetValueStaticHelper(full_key_name,
										value_name,
										REG_BINARY,
										&value,
										sizeof(value));
}

inline HRESULT RegKey::SetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								double value) {
									ASSERT1(full_key_name);

									return SetValueStaticHelper(full_key_name,
										value_name,
										REG_BINARY,
										&value,
										sizeof(value));
}

inline HRESULT RegKey::SetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								const TCHAR* value) {
									ASSERT1(full_key_name);
									ASSERT1(value);

									return SetValueStaticHelper(full_key_name,
										value_name,
										REG_SZ,
										const_cast<TCHAR*>(value));
}

inline HRESULT RegKey::SetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								const byte* value,
								DWORD byte_count) {
									ASSERT1(full_key_name);

									return SetValueStaticHelper(full_key_name, value_name, REG_BINARY,
										const_cast<byte*>(value), byte_count);
}

inline HRESULT RegKey::SetValueMultiSZ(const TCHAR* full_key_name,
									   const TCHAR* value_name,
									   const byte* value,
									   DWORD byte_count) {
										   ASSERT1(full_key_name);

										   return SetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ,
											   const_cast<byte*>(value), byte_count);
}

inline HRESULT RegKey::SetValueExpandSZ(const TCHAR* full_key_name,
										const TCHAR* value_name,
										const TCHAR* value) {
											ASSERT1(full_key_name);
											ASSERT1(value);

											return SetValueStaticHelper(full_key_name,
												value_name,
												REG_EXPAND_SZ,
												const_cast<TCHAR*>(value));
}

// GETTERS static versions
inline HRESULT RegKey::GetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								DWORD* value) {
									ASSERT1(full_key_name);
									ASSERT1(value);

									return GetValueStaticHelper(full_key_name, value_name, REG_DWORD, value);
}

inline HRESULT RegKey::GetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								DWORD64* value) {
									ASSERT1(full_key_name);
									ASSERT1(value);

									return GetValueStaticHelper(full_key_name, value_name, REG_QWORD, value);
}

inline HRESULT RegKey::GetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								float* value) {
									ASSERT1(value);
									ASSERT1(value_name);
									ASSERT1(full_key_name);

									DWORD byte_count = 0;
									byte* buffer = NULL;
									HRESULT hr = GetValueStaticHelper(full_key_name,
										value_name,
										REG_BINARY,
										&buffer,
										&byte_count);
									scoped_array<byte> free_buffer(buffer);

									if (SUCCEEDED(hr)) {
										if (byte_count == sizeof(*value)) {
											::CopyMemory(value, buffer, sizeof(*value));
										} else {
											UTIL_LOG(LEVEL_ERROR, (_T("[RegKey::GetValue]")
												_T("[size mismatches for float value][%s\\%s]"),
												full_key_name, value_name));
											return HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
										}
									}

									return hr;
}

inline HRESULT RegKey::GetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								double* value) {
									ASSERT1(value);
									ASSERT1(value_name);
									ASSERT1(full_key_name);

									DWORD byte_count = 0;
									byte* buffer = NULL;
									HRESULT hr = GetValueStaticHelper(full_key_name,
										value_name,
										REG_BINARY,
										&buffer,
										&byte_count);
									scoped_array<byte> free_buffer(buffer);

									if (SUCCEEDED(hr)) {
										if (byte_count == sizeof(*value)) {
											::CopyMemory(value, buffer, sizeof(*value));
										} else {
											UTIL_LOG(LEVEL_ERROR, (_T("[RegKey::GetValue]")
												_T("[size mismatches for double value][%s\\%s]"),
												full_key_name, value_name));
											return HRESULT_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
										}
									}

									return hr;
}

inline HRESULT RegKey::GetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								TCHAR** value) {
									ASSERT1(full_key_name);
									ASSERT1(value);

									return GetValueStaticHelper(full_key_name, value_name, REG_SZ, value);
}

inline HRESULT RegKey::GetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								CString* value) {
									ASSERT1(full_key_name);
									ASSERT1(value);

									TCHAR* buffer = NULL;
									HRESULT hr = RegKey::GetValue(full_key_name, value_name, &buffer);
									value->SetString(buffer);
									delete [] buffer;
									return hr;
}

inline HRESULT RegKey::GetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								std::vector<CString>* value) {
									ASSERT1(full_key_name);
									ASSERT1(value);

									return GetValueStaticHelper(full_key_name, value_name, REG_MULTI_SZ, value);
}

inline HRESULT RegKey::GetValue(const TCHAR* full_key_name,
								const TCHAR* value_name,
								byte** value,
								DWORD* byte_count) {
									ASSERT1(full_key_name);
									ASSERT1(value);
									ASSERT1(byte_count);

									return GetValueStaticHelper(full_key_name,
										value_name,
										REG_BINARY,
										value,
										byte_count);
}

template<typename T>
HRESULT RegKey::GetValue(const TCHAR* full_key_names[],
						 int key_names_length,
						 const TCHAR* value_name,
						 T* value) {
							 HRESULT hr = S_OK;
							 for (int i = 0; i < key_names_length; ++i) {
								 hr = GetValue(full_key_names[i], value_name, value);
								 if (SUCCEEDED(hr)) {
									 return hr;
								 }
							 }
							 return hr;
}

// Rename a named value.
inline HRESULT RegKey::RenameValue(const TCHAR * full_key_name,
								   const TCHAR * old_value_name,
								   const TCHAR * new_value_name) {
									   ASSERT1(full_key_name);

									   RegKey reg_key;
									   HRESULT hr = reg_key.Open(full_key_name);
									   if (FAILED(hr)) {
										   return hr;
									   }

									   return reg_key.RenameValue(old_value_name, new_value_name);
}

inline HRESULT RegKey::CopyValue(const TCHAR * full_from_key_name,
								 const TCHAR * full_to_key_name,
								 const TCHAR * value_name) {
									 return CopyValue(full_from_key_name,
										 value_name,
										 full_to_key_name,
										 value_name);
}

// DELETE
inline HRESULT RegKey::DeleteSubKey(const TCHAR* key_name) {
	ASSERT1(key_name);
	ASSERT1(h_key_);

	LONG res = ::RegDeleteKey(h_key_, key_name);
	HRESULT hr = HRESULT_FROM_WIN32(res);
	if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
		hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) {
			hr = S_FALSE;
	}
	return hr;
}

inline HRESULT RegKey::DeleteValue(const TCHAR* value_name) const {
	ASSERT1(value_name);
	ASSERT1(h_key_);

	LONG res = ::RegDeleteValue(h_key_, value_name);
	HRESULT hr = HRESULT_FROM_WIN32(res);
	if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
		hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) {
			hr = S_FALSE;
	}
	return hr;
}
#endif	// LONGKEY_BASE_REG_KEY_H_